package cn.xeblog.commons.factory;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ClassUtil;
import cn.xeblog.commons.util.ClassUtils;

import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * 单例工厂抽象类
 *
 * @author anlingyi
 * @date 2021/8/21 6:19 下午
 */
public abstract class AbstractSingletonFactory<K, V> {
    /**
     * 注册表
     */
    private final Registry<K, V> registry;

    /**
     * 默认构造
     */
    public AbstractSingletonFactory() {
        // 构建注册表
        this.registry = setRegistry();
        // 注册实例
        this.registration(registry);
    }

    /**
     * 构建一个空注册表
     *
     * @return 空注册表
     */
    protected Registry<K, V> setRegistry() {
        return new Registry(null);
    }

    /**
     * 获取注册的实例
     *
     * @param key 映射维护的键类型
     * @return 映射值的类型
     */
    public V produce(K key) {
        return this.registry.getInstance(key);
    }

    /**
     * 注册实例
     *
     * @param registry 注册表对象
     */
    protected abstract void registration(Registry<K, V> registry);

    /**
     * 注册表
     *
     * @param <K> 此映射维护的键类型
     * @param <V> 映射值的类型
     */
    protected class Registry<K, V> {
        /**
         * jarPath主要是针对插件端，插件端打包后的jar目录比较特殊，需要插件端传递jar包路径,不然无法扫描到类文件
         */
        private final String jarPath;
        /**
         * 缓存的实例
         */
        private final Map<K, V> instanceCaches = new HashMap<>();

        /**
         * 通过jar路径构建注册表对象
         *
         * @param jarPath jar路径
         */
        public Registry(String jarPath) {
            this.jarPath = jarPath;
        }

        /**
         * 添加实例
         *
         * @param key   此映射维护的键类型
         * @param clazz 指定类型的class对象
         */
        public void add(K key, Class<? extends V> clazz) {
            try {
                addInstance(key, clazz.newInstance());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        /**
         * 通过注解扫描来添加注解
         *
         * @param packageName     包路径
         * @param annotationClass 注解class对象
         */
        public void addByAnnotation(String packageName, Class<? extends Annotation> annotationClass) {
            Set<Class<?>> classes;
            // 通过类路径或者包路径或者注解来获取相应的class对象
            classes = ClassUtils.scan(this.jarPath, packageName, annotationClass);
            if (!CollectionUtil.isEmpty(classes)) {
                classes.forEach(clazz -> {
                    add(AnnotationUtil.getAnnotationValue(clazz, annotationClass), (Class<? extends V>) clazz);
                });
            }
        }

        /**
         * 通过注解扫描来添加注解
         *
         * @param annotationClass 注解class对象
         */
        public void addByAnnotation(Class<? extends Annotation> annotationClass) {
            addByAnnotation(null, annotationClass);
        }

        /**
         * 添加实例
         *
         * @param k 此映射维护的键类型
         * @param v 映射值的类型
         */
        private void addInstance(K k, V v) {
            this.instanceCaches.put(k, v);
        }

        /**
         * 通过key获取映射值
         *
         * @param k 维护映射的键
         * @return 键的映射值
         */
        private V getInstance(K k) {
            return this.instanceCaches.get(k);
        }
    }

}
